#This class is responsible for parsing the input, line by line.

from errors import * #programming error handling
from func import * #builtin and user function handling
import types #type control


class Parser:
 def __init__(self, stack): #requires the stack class to pass on to the function handling information. This stack is created higher up so the interpriter can give feedback.
  #store local references.
  self.stack=stack
  self.func=Functions(self.stack,self) #Create function handler and allow it to control parsing in some instances.
  #identify where we are in the file.
  self.lineCount=0 
  self.wordCount=0
  self.lastWord=""
  self.words=[] #all the words in the current line.
  #setup variables for parsing functions and loops.
  #see preserved function
  self.preserved=False 
  self.preservedList=[]

 #When handling some statements like loops and functions, we just parse the string making up that function. This allows us to do that by storing the current information.
 #When parsing functions and loops, we don't want to: loose the words after the contents of the loop, and increase line and word positional information.
 def preserve(self):
  self.preserved=True #flag to prevent updating of positional info.
  self.preservedList.append(self.words) #store current words left in the line.

 #Undo the last preserve.
 def restore(self):
  self.words=self.preservedList.pop() #get the remainder of the last saved line.
  self.preserved=(len(self.preservedList) != 0) #set the preserved flag based on weather there are any more line remainders in storage.
  self.HandleLine() #handle this line.

 #how many words remaining in this line.
 def Count(self): 
  return len(self.words)

 #continue parsing?
 def WordsQueued(self):
  return self.Count() > 0  #if there are more words.

#get the next word.
 def pop(self):
  #if not in a preserved state,
  if not self.preserved:
   self.wordCount += 1 #update positional info.
  g= self.words.pop().strip() #get a word and remove white space.
  while g == "": #if it's only white space,
   if self.WordsQueued(): #and if there are more words,
    g= self.words.pop().strip() #try another word.
  #now that we found one or ran out of words, update positional information.
  if not self.preserved: 
   self.lastWord=g
  return g #return the good word.

 #locate the ends of things like: function deffinitions, if statements, loops, and strings.
 def FindToken(self,token):
  tm=-1 #token position
  lst=[] #end result
  self.words.reverse() #reversed it earlyer so we could pop from stack.
  if self.words.count(token) >= 1: #if the token is a word:
     tm=self.words.index(token) #mark its position.
  else: #if it's only part of a word,
   for i in range(len(self.words)): #go through all the words.
    if self.words[i].count(token) >= 1: #if the token is in this word,
      tm=i #mark its position.
      break #stop looking for tokens.
  if tm >= 0: #if we found the token,
   lst=self.words[:tm+1] #get the words, including the token.
  self.words=self.words[tm+1:] #remove all the words from the list of remaining words on this line, they've been handled.
  self.words.reverse() #lets pop again.
  return lst 


 #When a word is made up of other words instead of builtin functions, this handles the parsing of the words. 
 def ParseFunc(self, f): #f = the string of words.
  self.preserve() #preserve current state.
  self.ParseLine(f) #parse the new function.
  self.restore() #restore the state.

 #handle a group of words, most likely one line.
 def ParseLine(self, line):  #line = the line of data. Can be more than 1 line if joined with " " instead of "\n".
  #if state is not preserved,
  if not self.preserved:
   #new line, reset word count and incriment line counter.
   self.lineCount+=1 
   self.wordCount=0
  line=line.strip() #remove white space.
  #discard blank lines.
  if line == "":
   return ""
  else:
   self.words=line.split(" ") #get each word.
   self.words.reverse() #set up for pop.
   #update position info.
   if not self.preserved:
    self.line=line
   self.HandleLine() #act.

 #This function feeds words to the word parser.
 def HandleLine(self):
  while self.WordsQueued(): #while there are words,
   try:
    self.ParseWord(self.pop()) #carry out the word
   except CompilingErrorException as x: #catch programming errors.
    raise CompilingErrorException("There was an error on line " + str(self.lineCount) + " at word " + str(self.wordCount) + ".\n" + x.message + "\nThe word was \"" + self.lastWord+"\". and the line was:\n" + self.line) #raise the programming error with more information about the position of the error.

 #This is the function that decides what to do with each word.
 def ParseWord(self, word):
  #discard empty words.
  if word == "": 
   pass
  #check for the start of a string and build it.
  if word[0]=="\"": 
   self.stack.push(self.BuildString(word))
  #look for a symbol.
  elif self.func.isSymbol(word):
   f =self.func.GetSymbol(word) #get the value.

   self.stack.push(f) #put the value on the stack.
  #if the function exists, get it.
  elif self.func.isFunc(word):
   f =self.func.GetFunc(word)
   if type(f) == types.StringType: #if the function is defined as words, 
    self.ParseFunc(f) #parse the words.
   else:
    f() #if it's a hard-coded function, call it.
  else: #it's not a function.
   #try to convert it to a number.
   try:
    f=int(word) #convert to int.
    self.stack.push(f) #add it to the stack.
   except ValueError: #oops, it's not an int.
    try: #Let's see if it's a float.
     f=float(word) #convert to float.
     self.stack.push(f) #if it is, add to stack.
    except ValueError: #oops, not a float.
     #At present, no other types are recognised, only string, int, and float.
     raise CompilingErrorException("Unfortunately, there was an unrecognised word. Did you forget quotation marks?")    #programming error.

 #build a string by finding the last quotation and joining the words in a string.
 def BuildString(self, word): #takes the first word sence it's no longer in the list.
  r="" #start with empty string.
  #If the last char is " and it's not only 1 char long, it's a string. 
  if word[-1] == "\"" and len(word) > 1: 
   r=word
  else: #It's more than word long.
   lst=self.FindToken("\"") #find the next ", the end of the string.
   #if there are words in the list, make it the string.
   if len(lst) > 0:
    r= word + " " + " ".join(lst)
  #if after all that we have a string, strip the quotes.
  if r != "":
   return r[1:-1]
  else: #the second " was forgotten.
   raise CompilingErrorException("End of line was reached without finding (\").") #programming error.
